# 雪花算法生成全局唯一id，依赖时钟，线程不安全

import time

# dataCenterId占用的位数：2
DATA_CENTER_ID_BITS = 2
# dataCenterId可以使用范围：0-3
MAX_DATA_CENTER_ID = ~(-1 << DATA_CENTER_ID_BITS)
# workerId占用的位数：8
WORKER_ID_BITS = 8
# workerId可以使用范围：0 - 255
MAX_WORKER_ID = ~(-1 << WORKER_ID_BITS)
# 序列号占用的位数：12（表示只允许workId的范围为：0-4095）
SEQUENCE_BITS = 12
# 用mask防止溢出:位与运算保证计算的结果范围始终是 0-4095
SEQUENCE_MASK = ~(-1 << SEQUENCE_BITS)
# 起始时间戳
START_TIME = 1519740777809

WORKER_ID_SHIFT = SEQUENCE_BITS
DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS
TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS


class IdWorker(object):
    """
    用于生成IDs
    """

    def __init__(self, datacenter_id: int, worker_id: int, time_offset: int = 5):
        """
        初始化
        :param datacenter_id: 数据中心ID,数据范围为0~255
        :param worker_id: 工作机器ID,数据范围为0~3
        :param time_offset: 允许时间回拨的毫秒量,建议5ms
        """
        if datacenter_id > MAX_DATA_CENTER_ID or datacenter_id < 0:
            raise Exception(f"Data Center Id can't be greater than {MAX_DATA_CENTER_ID} or less than 0")
        if worker_id > MAX_WORKER_ID or worker_id < 0:
            raise Exception(f"Worker Id can't be greater than {MAX_WORKER_ID} or less than 0")
        self.worker_id = worker_id
        self.datacenter_id = datacenter_id
        self.time_offset = time_offset
        self.last_timestamp = -1
        self.sequence = 0

    @staticmethod
    def get_time() -> int:
        """
        生成毫秒时间戳
        :rtype: int
        :return: int 毫秒时间戳
        """
        return int(time.time() * 1000)

    def til_next_millis(self, last_timestamp: int) -> int:
        """
        等到下一毫秒
        :rtype: int
        :return: 新的时间戳
        """
        timestamp = self.get_time()
        while timestamp <= last_timestamp:
            # 如果发现时间回拨，则自动重新获取（可能会处于无限循环中）
            timestamp = self.get_time()
        return timestamp

    def next(self):
        current_timestamp = self.get_time()

        # 闰秒：如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过，这个时候应当抛出异常
        if current_timestamp < self.last_timestamp:
            print('润秒')
            # 校验时间偏移回拨量
            offset = self.last_timestamp - current_timestamp
            if offset > self.time_offset:
                raise Exception(f"Clock moved backwards, refusing to generate id for [{offset}ms]")
            # 时间回退timeOffset毫秒内，则允许等待2倍的偏移量后重新获取，解决小范围的时间回拨问题
            time.sleep(offset << 1)
            # 再次获取
            current_timestamp = self.get_time()
            # 再次校验
            if current_timestamp < self.last_timestamp:
                raise Exception(f"Clock moved backwards, refusing to generate id for [{offset}ms]")

        # 同一毫秒内序列直接自增
        if self.last_timestamp == current_timestamp:
            # randomSequence为true表示随机生成允许范围内的序列起始值并取余数, 否则毫秒内起始值为0L开始自增
            temp_sequence = self.sequence + 1
            # 通过位与运算保证计算的结果范围始终是0-4095
            self.sequence = temp_sequence & SEQUENCE_MASK
            if self.sequence == 0:
                current_timestamp = self.til_next_millis(self.last_timestamp)
        else:
            # randomSequence为true表示随机生成允许范围内的序列起始值, 否则毫秒内起始值为0L开始自增
            self.sequence = 0

        self.last_timestamp = current_timestamp
        current_offset_time = current_timestamp - START_TIME

        # 1.左移运算是为了将数值移动到对应的段(41、5、5，12那段因为本来就在最右，因此不用左移)
        # 2.然后对每个左移后的值(la、lb、lc、sequence)做位或运算，是为了把各个短的数据合并起来，合并成一个二进制数
        # 3.最后转换成10进制，就是最终生成的id
        return (current_offset_time << TIMESTAMP_LEFT_SHIFT) | \
               (self.datacenter_id << DATA_CENTER_ID_SHIFT) | \
               (self.worker_id << WORKER_ID_SHIFT) | self.sequence


id_worker = IdWorker(1, 1)


def next_id() -> int:
    return id_worker.next()


if __name__ == '__main__':
    for i in range(100):
        print(next_id())
